; 编写一个完整的COM汇编程序（nfs），通过键盘接受用户的十进制数串的输入，
; 将该数字串转换为无符号整数n；计算并输出（计算结果的十进制数串）n! 和Sum =1+2+……+n。
; 屏幕I/O的显示如（圆括号内为注释）：
; Please input n: （显示提示串）
; 12 （用户键入n）
; n = 12 （数字串 -> 整数 -> 数字串 -> 显示串）
; n! = 479,001,600 （可能需要判断是否有溢出，如EDX >0？）
; Sum = 78 （前n个自然数的和）
    org 0100h
    mov ax, cs
    mov es, ax
    mov ds, ax
    mov ss, ax  ; 设置堆栈指针
    mov sp, 100h - 4
	call PrintHead
	; jmp $
    mov ax,4c00h    ; AH=4Ch（功能号，终止进程）、AL=0（返回代码）
    int 21h         ; DOS软中断

PrintHead:
; 显示字符串2 "Please input n: (Press Enter key to end)"
    mov bp, str2 ; BP=串地址
    mov cx, strlentwo ; 串长
    call DispStr
	call newline
; 读取并保存 倒序ascii码
    mov cx, 1
    mov bp, ssbuf
    add bp, dns - 1
Keyin: ; 读按键
	mov ah, 0 	; 0号功能（读按键）
	int 16h 		; 调用16H号键盘中断
	cmp al, 0dh     ; 比较AL中的键入字符与回车字符（ASCII码为0x0D）,回车键则停止输入
	je GetBin        ; 相等
	; echo
    mov ah, 0eh 	; 功能号
    mov bl, 0 		; 对文本方式置0
    int 10h 		; 调用10H号中断
	; save
	mov [bp], al
	dec bp
	inc cx
	jmp Keyin
GetBin: ; 真值保存在eax中
	call newline
	mov eax, 0
	dec cx
	add bp, cx
inLoop:
	mov ebx, 0
	mov bl, [bp]
	sub bl, 30h
	add eax, ebx
	dec bp
	dec cx
	cmp cx, 0
	je goout
	mov ebx, 10
	mul ebx
	jmp inLoop
goout:
; 打印n真值
	push eax ; 保存user输入的真值
	mov bp, strn
	mov cx, strnlen
	call DispStr
	pop eax
	push eax
	call GetDigStr
	call DispStr
	call newline
	; 打印阶乘结果
	mov bp, strFa
	mov cx, strFalen
	call DispStr
	pop eax
	push eax
	mov cl, al
	call Factorial
	cmp edx, 0
	jg Overflow
	call GetDigStr
	call DispStr
	call newline
	jmp GetSum
Overflow:
	mov bp, strOF
	mov cx, strOFlen
	call DispStr
	call newline
GetSum:
	mov bp, strSum
	mov cx, strSlen
	call DispStr
	pop eax
	mov cx, ax
	call Sum
	call GetDigStr
	call DispStr
	call newline
	ret

; --------------------
newline:
;newline
	mov al,0dh ; 回行首
	mov ah,0eh ; 功能号
    mov bl,0 ; 对文本方式置0
    int 10h ; 调用10H号中断
	mov al,0ah ; 换行
	mov ah,0eh ; 功能号
    mov bl,0 ; 对文本方式置0
    int 10h ; 调用10H号中断
	ret

; --------------------------------------------------------------------
DispStr: ; 显示字符串例程（需先置串长CX和串地址BP）
	; 获取当前光标位置（返回的行列号分别在DH和DL中）
	push cx			; 保护CX（进栈）
	mov ah, 3		; 功能号
	mov bh, 0		; 第0页
	int 10h 		; 调用10H号显示中断
	pop cx			; 恢复CX（出栈）

	; 在当前位置显示字符串（串长CX和串地址BP已预先设置好了）
	mov ah, 13h		; BIOS中断的功能号（显示字符串）
	mov al, 1 		; 光标放到串尾
	mov bh, 0 		; 页号=0
	mov bl, 0fh		; 字符颜色=不闪（0）黑底（000）亮白字（1111）
	int 10h 		; 调用10H号显示中断
	ret				; 从例程返回

; ----------
GetDigStr: ; 以EAX为传递参数，[串地址]BP和[字符个数]CX为返回值; 真值->ascii
	mov cx, 1			; 位数=1（初值）
	mov bp, sbuf		; BP = sbuf + dn - 1 = sbuf的当前位置
	add bp, dn - 1
	mov bx,10		; 除数=10
BLoop: ; 循环开始处
	mov edx, 0		; EDX=0
	div ebx			; eax ← edx:eax/源 的商、edx ← edx:eax/源 的余数
	add dl, 30h		; 余数 + 30h = 对应的数字符ASCII码
	mov [bp], dl		; sbuf[BP] = DL
	cmp eax, 0			; 商AX = 0 ?
	je ELoop		; = 0 跳出循环
	inc cx			; 位数CX++
	dec bp			; 数字符的当前位置BP--
	jmp BLoop		; 继续循环
ELoop: ; 退出循环
	ret

; -----------
Factorial: ; 以CL为传递参数，返回值在EAX中
	mov edx, 0	; EDX = 0
	mov eax, 1	; n! = EDX:EAX = 1（初值）
.11: ; 循环开始处
	cmp cl, 0		; CL = 0 ?
	je .12			; = 0 跳出循环
	movzx ebx, cl	; EBX = CL
	mul ebx		; EAX * EBX -> EDX:EAX
	cmp edx, 0
	jg OF
	dec cl		; CL--
	jmp .11		; 继续循环
.12: ; 退出循环 ; 
	ret
OF:
	mov edx, 10
	ret
; --------------------------------------------------------------------
Sum: ; 以CX为传递参数，返回值在EAX中
	mov eax, 0	; S = EAX = 0（初值）
	movzx ecx, cx	; ECX = CX
.21: ; 循环开始处
	cmp ecx, 0	; ECX = 0 ?
	je .22			; = 0 跳出循环
	add eax, ecx	; EAX + ECX -> EAX
	dec ecx		; ECX--
	jmp .21		; 继续循环
.22: ; 退出循环
	ret			; 从例程返回

; ----data-----
	
str2: 
	db "Please input n: (Press Enter key to end) "
	strlentwo equ $ - str2 ; 定义串2长常量
strn:
	db "n = "
	strnlen equ $ - strn
strFa:
	db "n! = "
	strFalen equ $ - strFa
strSum:
	db "Sum = "
	strSlen equ $ - strSum
strOF:
	db "Overflow!"
	strOFlen equ $ - strOF

dns equ 16
ssbuf: db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0  ; 用于存放十六进制数字串的缓冲区

dn equ 16 ; 最大位数
sbuf: db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ; 用于存放十进制数字串的缓冲区
